﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using MyUtils;
using XTrace.Registry;

namespace XTrace.FileLoggers
{
	public class UniversalListener : XTraceListener
	{
		readonly Formatter _formatter;
		readonly Logger _logger;

		public UniversalListener(Formatter formatter, Logger logger)
		{
			_formatter = formatter;
			_logger = logger;
		}

		public override void Event(XTraceEvent traceEvent)
		{
			var entry = _formatter.Serialzie(traceEvent);
			_logger.LogEntry(entry);
		}
	}

	public interface ISystemClock
	{
		DateTime UtcNow { get; }
	}

	public class RealSystemClock : ISystemClock
	{
		public DateTime UtcNow
		{
			get { return DateTime.UtcNow; }
		}
	}

	public abstract class Logger : IDisposable
	{
		protected readonly LoggerRollOverSettings _rollover;

		public Logger(LoggerRollOverSettings rollover = null)
		{
			_rollover = rollover;
		}

		public abstract void LogEntry(byte[] entry);
		public abstract void Dispose();
	}

	public class FileLogger : Logger
	{
		readonly RelativePath _relativePathAction = RelativePath.ThrowException;
		string _fullPathFormat;

		FileStream _stream;
		string _autoExtension;

		public FileLogger(string easyPath, LoggerRollOverSettings rollover = null, RelativePath relativePathActionAction = RelativePath.ThrowException, string autoExtension = "xlog")
			: base(rollover)
		{
			_relativePathAction = relativePathActionAction;
			_autoExtension = autoExtension;
			_fullPathFormat = GetFullPathFormat(easyPath);
		}

		string GetFileName(string easyPath, string rolloverIdentity)
		{
			return GetFullPathFormat(easyPath).ArgI(rolloverIdentity);
		}

		string GetFullPathFormat(string easyPath)
		{
			string fullPathFormat;
			if (!Path.IsPathRooted(easyPath))
			{
				switch (_relativePathAction)
				{
					case RelativePath.ThrowException:
						throw new ArgumentException(@"Path is not rooted", "easyPath");
					case RelativePath.CurrentDir:
						fullPathFormat = Path.Combine(Directory.GetCurrentDirectory(), easyPath);
						break;
#if !PocketPC
					case RelativePath.EntryAssemblyLocation:
						fullPathFormat = Path.Combine(Path.Combine(Assembly.GetEntryAssembly().Location), easyPath);
						break;
#endif
					case RelativePath.TempFolder:
						fullPathFormat = Path.Combine(Path.Combine(Path.GetTempPath(), "_XTraceLogs"), easyPath);
						break;
					default:
						throw new ArgumentOutOfRangeException("RelativePath " + _relativePathAction);
				}
			}
			else
			{
				fullPathFormat = easyPath;
			}

			if (Path.GetExtension(fullPathFormat).IsNullOrWhitespaces())
			{
				fullPathFormat += "." + _autoExtension;
			}

			// insert formatting points
			var dir = Path.GetDirectoryName(fullPathFormat);
			var name = Path.GetFileNameWithoutExtension(fullPathFormat);
			var ext = Path.GetExtension(fullPathFormat);

			fullPathFormat = Path.Combine(dir, name + "_{0}" + ext);

//			if (File.Exists(easyPath))
//			{
//				if (SourcesProvider.FilesToRemove.Any(x => string.Equals(x, easyPath, StringComparison.InvariantCultureIgnoreCase)))
//				{
			// try to remove
//					SourcesProvider.MarkForClear(easyPath);
//				}
//			}
//			var dir = Path.GetDirectoryName(easyPath);
//			Directory.CreateDirectory(dir);
			return fullPathFormat;
		}

		void EnsureStream()
		{
			if (_stream == null)
			{
				
			}


		}

		private string RecycleLogFiles(string directoryName, string fileBaseName)
		{
			string[] fileNames = Directory.GetFiles(directoryName, GenerateName(null, fileBaseName, "??"));
			FileInfo[] files = fileNames.Select(x => new FileInfo(x)).OrderBy(x => x.LastWriteTimeUtc).ToArray();

			string resultName;

			// самый свежий
			FileInfo last = files.LastOrDefault();

			if (last == null)
			{
				// файлов нет, создаём первый файл
				resultName = GenerateName(directoryName, fileBaseName, 1);
			}
			else if (_sizeLimit == 0 || last.Length < _sizeLimit)
			{
				// продолжаем писать в последний файл, там еще есть место, либо место не ограничено
				resultName = last.FullName;
			}
			// последний файл переполнен :
			else if (files.Length >= _filesCount)
			{
				// количество файлов достигло максимума - пишем вместо самого старого
				FileInfo first = files.FirstOrDefault();
				resultName = first.FullName;
				File.Delete(resultName);
			}
			else
			{
				// еще есть слоты для файлов, но последний файл переполнен - пишем в следующий
				// todo если кто-то удалит средний файл из набора - будет не очень хорошо
				resultName = GenerateName(directoryName, fileBaseName, files.Length + 1);
			}

			return resultName;
		}

		readonly object _sync = new object();

		public override void LogEntry(byte[] entry)
		{
			lock (_sync)
			{
				EnsureStream();
				_stream.Write(entry, 0, entry.Length);
			}
		}

		public override void Dispose()
		{
			var stream = _stream;
			if (stream != null)
			{
				stream.Dispose();
			}
		}
	}

	public abstract class Formatter
	{
		public abstract byte[] Serialzie(XTraceEvent ev);
		public abstract XTraceEvent Deserialzie(byte[] bytes);
	}

	public class XTraceEventFormatterV1_2 : Formatter
	{
		static readonly Encoding Encoding = new UTF8Encoding(false, true);

		public override byte[] Serialzie(XTraceEvent ev)
		{
			return Encoding.GetBytes(ev.Serialize() + Environment.NewLine);
		}

		public override XTraceEvent Deserialzie(byte[] bytes)
		{
			var str = Encoding.GetString(bytes);
			str = str.TrimEnd('\r', '\n');
			return XTraceEvent.Deserialize(str);
		}
	}

	public abstract class LoggerRollOverSettings
	{
	}

	public enum RolloverSchedule
	{
		None,
		Hourly,
		Daily,
		Weekly,
		Monthly,
	}

	public class TimedLoggerRollOverSettings : LoggerRollOverSettings
	{
		public RolloverSchedule RolloverSchedule { get; private set; }

		public TimedLoggerRollOverSettings(RolloverSchedule rolloverSchedule)
		{
			RolloverSchedule = rolloverSchedule;
		}
	}

	public class SizeLoggerRollOverSettings : LoggerRollOverSettings
	{
		public SizeLoggerRollOverSettings(long sizeLimit)
		{
			SizeLimit = sizeLimit;
		}

		public SizeLoggerRollOverSettings(string sizeLimit)
		{
			SizeLimit = Utils.ParseSizeString(sizeLimit);
		}

		public long SizeLimit { get; private set; }

	}
}
